/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar.p071;

import java.net.MalformedURLException;
import java.net.URL;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.gson.JsonObject;
import com.lucky_byte.pay.jar.HttpClient;
import com.lucky_byte.pay.jar.JdbcRecord;
import com.lucky_byte.pay.jar.JdbcTable;

/**
 * 银联 ACP 接出渠道
 */
public class Fowder071
{
	private static final Logger logger = LogManager.getLogger();

	private JsonObject netinfo;
	private JdbcRecord record;

	public Fowder071() {
		this.netinfo = new JsonObject();
	}

	/**
	 * 返回网络通讯信息，用于记录流水
	 */
	public JsonObject getNetinfo() {
		return this.netinfo;
	}

	/**
	 * 设置网络通讯信息
	 */
	private void setNetinfo(HttpClient client) {
		this.netinfo.addProperty("c_addr", client.getLocalHost());
		this.netinfo.addProperty("c_port", client.getLocalPort());
		this.netinfo.addProperty("s_addr", client.getTargetURL());
		this.netinfo.addProperty("s_port", client.getTargetPort());
	}

	/**
	 * 转发报文
	 */
	public Packet071 forward(Packet071 packet, boolean indirect) {
		JdbcTable table = JdbcTable.listAll_NE("pay_o_071");
		if (table == null || table.getRecordCount() == 0) {
			logger.error("银联 ACP 接出渠道尚未配置，请检查...");
			return null;
		}
		this.record = table.getRecord(0);
		return this.doPost(packet);
	}

	/**
	 * 发送 HTTP POST 请求到服务器并接收响应，不管服务器返回任何代码，
	 * 如果有返回数据，都会尝试返回到调用者.
	 */
	private Packet071 doPost(Packet071 packet) {
		String txn_class = this.getTxnClass(packet);
		if (txn_class == null) {
			return null;
		}
		// 后台交易添加交易结果异步通知地址
		if (txn_class == "url_back") {
			String notify_url = this.record.getString("back_notify_url");
			if (notify_url == null || notify_url.trim().length() == 0) {
				logger.error("没有配置 ACP 后台通知地址，请检查...");
				return null;
			}
			packet.set("backUrl", notify_url);
		}
		// 获取服务地址
		URL target_url = this.getTargetURL(txn_class);
		if (target_url == null) {
			return null;
		}
		// 设置报文的渠道类型
		String chnl_type = this.record.getString("chnl_type");
		if (chnl_type == null || chnl_type.length() != 2) {
			logger.warn("ACP 接出配置中未配置渠道类型或配置值无效，使用默认 07.");
			packet.set("channelType", "07");
		} else {
			packet.set("channelType", chnl_type);
		}
		// 加密敏感字段
		if (this.record.getBool("v13_enc")) {
			if (!Lib071.encrypt(packet)) {
				return null;
			}
		}
		String merno = packet.get("merId");
		if (merno == null) {
			logger.error("转发报文中缺少[merId]字段，不能转发.");
			return null;
		}
		// 对报文进行签名
		String sign_key_name = "acp_sign_" + merno;
		if (!Lib071.sign(packet, sign_key_name)) {
			logger.error("转发报文签名失败，停止转发.");
			return null;
		}
		// 通讯超时时间设置，不能太大或太小
		Integer timeout = this.record.getInteger("timeout");
		if (timeout == null || timeout > 30 || timeout < 5) {
			timeout = 10;
		}
		HttpClient client = new HttpClient(target_url, timeout,
				packet.get("encoding"), true);
		int resp_code = client.doPost(packet.pack());
		this.setNetinfo(client);

		if (resp_code == -1) {
			logger.error("发送HTTP POST请求到[{}]失败.", target_url.toString());
			return null;
		}
		if (resp_code != 200) {
			logger.error("服务器响应 HTTP 状态码非 200，不处理响应数据，交易失败.");
			return null;
		}
		Packet071 respkt = new Packet071();
		if (!respkt.parse(client.getResponse())) {
			logger.error("解析响应报文失败.");
			return null;
		}
		// 验证响应报文签名
		String vers_key_name = "acp_vers_" + merno;
		if (!Lib071.verifySign(respkt, vers_key_name)) {
			logger.error("验证响应报文签名失败.");
			return null;
		}
		logger.trace("验证响应报文签名通过...");
		return respkt;
	}

	/**
	 * 通过交易码映射到不同的 URL，银联 ACP 提供多个 URL 地址
	 */
	private final String[][] TYPE_TARGET_URL_MAP = {
			{ "00", "url_query" },	// 查询交易
			{ "01", "url_back" },	// 消费
			{ "02", "url_back" },	// 预授权
			{ "03", "url_back" },	// 预授权完成
			{ "04", "url_back" },	// 退货
			{ "05", "url_back" },	// 圈存
			{ "11", "url_back" },	// 代收
			{ "12", "url_back" },	// 代付
			{ "13", "url_back" },	// 账单支付
			{ "14", "url_back" },	// 转账(保留)
			{ "21", "url_batch" },	// 批量交易
			{ "22", "url_batch" },	// 批量查询
			{ "31", "url_back" },	// 消费撤销
			{ "32", "url_back" },	// 预授权撤销
			{ "33", "url_back" },	// 预授权完成撤销
			{ "71", "url_query" },	// 余额查询
			{ "72", "url_back" },	// 实名认证-建立绑定关系
			{ "73", "url_query" },	// 账单查询
			{ "74", "url_back" },	// 解除绑定关系
			{ "75", "url_query" },	// 查询绑定关系
			{ "76", "url_file" },	// 文件传输交易
			{ "77", "url_back" },	// 发送短信验证码交易
			{ "78", "url_back" },	// 开通查询交易
			{ "79", "url_back" },	// 开通交易
			{ "94", "url_back" },	// IC 卡脚本交易
	};

	/**
	 * 对报文进行分类
	 */
	private String getTxnClass(Packet071 packet) {
		String txn_type = packet.get("txnType");
		if (txn_type == null) {
			logger.error("报文缺少[txnType]字段，不能确定交易分类.");
			return null;
		}
		for (String[] entry : TYPE_TARGET_URL_MAP) {
			if (entry[0].equals(txn_type)) {
				return entry[1];
			}
		}
		logger.error("不能发送[txnType]为[{}]的交易.", txn_type);
		return null;
	}

	/**
	 * 通过报文类型确定外出 URL 地址
	 */
	private URL getTargetURL(String txn_class) {
		String target_url = this.record.getString(txn_class);
		if (target_url == null || target_url.length() == 0) {
			logger.error("银联 ACP 接出配置中服务地址[{}]未配置.", txn_class);
			return null;
		}
		logger.debug("当前交易将发送到[{}]...", target_url);

		try {
			URL url = new URL(target_url);
			String proto = url.getProtocol();
			if (!proto.equalsIgnoreCase("http") && !proto.equalsIgnoreCase("https")) {
				logger.error("银联 ACP 不支持通信协议[{}].", proto);
				return null;
			}
			return url;
		} catch (MalformedURLException e) {
			logger.error("银联 ACP 接出配置中到URL[{}]语法错误[{}][{}]",
					target_url, e.getMessage(), e.getClass().getSimpleName());
			return null;
		}
	}

}
